package base.thread_demo.demo14;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * jdk1.5以后读写锁演示 ReadWriteLock
 * 读写互斥锁 ReadWriteLock 实现类 ReentrantReadWriteLock
 * 读锁:用在数据的读取
 * 写锁:用在数据的赋值
 * 可以实现对共享数据的读不保护，对写保护。就是说，t1读了后，t2还可以读，不影响后续线程的读操作，影响的是此时
 * 写线程发现此共享数据上有读操作，写不了。
 *
 * 如果t1进行的是写操作，加上了写锁，那么后续线程不能进行读和写，直到该写线程释放了写锁。这样就起到写保护作用。
 * 还有一点需要注意的是：我这个线程自己挂的写锁，还可以挂上读锁，此时是将自己这个写锁降级，挂上自己的读锁。
 *
 */
public class ReadWriteLockDemo1 {

    public static void main(String[] args) {
        final Queue queue = new Queue();
        // 启动3个写线程,3个读线程
        for (int i = 0; i < 3; i++) {
            // 负责不停的写入数据
            new Thread(()->{
                while (true){
                    try {
                        queue.put(new Random().nextInt(10000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"W"+i).start();

            // 负责不停的读取数据
            new Thread(()->{
                while (true) {
                    try {
                        queue.get();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"R"+i).start();
        }
    }


    /**
     * 读写动作要互斥,将读写动作封装到一个类中进行控制
     * ReadWriteLock 可以完成以下控制:
     * 一个线程在读的过程中(开始读取-读取结束),其他线程可以读,但不可以写;
     * 一个线程在写的过程中(开始写入-写入结束),其他线程不可以读和写;
     *
     * ReentrantReadWriteLock 是 ReadWriteLock 的一个实现
     */
    static class Queue {
        private static final Logger log = LoggerFactory.getLogger(Queue.class);
        private Object data;// 共享数据,同时只能有一个线程写该数据,但可以有多个线程同时读该数据
        ReadWriteLock rwl = new ReentrantReadWriteLock();

        /**
         * 读动作
         *
         */
        public void get() throws InterruptedException {
            /**
             * R1假使在这里 rwl.readLock().lock()了,R2还是可以在这里 rwl.readLock().lock()的,
             * 但是W1就不能对这里的共享变量 this.data 上 rwl.writeLock().lock()锁了.
             */
            rwl.readLock().lock();
            try {
                log.info("-------------->{}: 开始读取数据",Thread.currentThread().getName());
                /**
                 * 模拟读取过程中,读锁没有让写线程写入,但其他读线程可以进入去读.
                 * 如果"开始读取"至"读取完毕"这个过程中有写动作,就说明读写锁没有控制住
                 */
//                Thread.sleep(10);
                log.info("-------------->{}: 读取完毕:{}",Thread.currentThread().getName(), this.data);
            }finally {
                rwl.readLock().unlock();
            }
        }

        /**
         * 写动作
         *
         */
        public void put(int data) throws InterruptedException {
            rwl.writeLock().lock();
            try {
                log.info("{}: 开始写入数据",Thread.currentThread().getName());
                /**
                 * 模拟写过程中,写锁没有让读线程读取,也没有让其他写线程进去写.
                 * 如果"开始写入"至"写入完毕"这个过程中有读动作或者有其他线程写了,就说明读写锁没有控制住
                 */
//                Thread.sleep(10);
                this.data = data;
                log.info("{}: 写入完毕:{}",Thread.currentThread().getName(), this.data);
            }finally {
                rwl.writeLock().unlock();
            }
        }
    }
}
